package com.power.leopard.remote;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.power.leopard.common.util.ClassUtils;
import com.power.leopard.constants.Constants;
import com.power.leopard.remote.provider.domain.InvocationFailureException;

public class ServiceMethodCache {

	private int methodSize = 0;

	private Object service;

	private ServiceMethod currentMethod;

	private Map<String, Map<Integer, List<ServiceMethod>>> methods = new ConcurrentHashMap<String, Map<Integer, List<ServiceMethod>>>();

	private Map<String, Map<ServiceParam, ServiceMethod>> bestMacthMethod = new ConcurrentHashMap<String, Map<ServiceParam, ServiceMethod>>();

	public ServiceMethodCache(String serviceName, Object service) {
		// this.serviceName = serviceName;
		this.service = service;
	}

	public void add(ServiceMethod serviceMethod) {
		if (currentMethod == null) {
			currentMethod = serviceMethod;
		}
		Method method = serviceMethod.getMethod();
		String name = method.getName();

		Map<Integer, List<ServiceMethod>> map = methods.get(name);
		if (map == null) {
			methods.put(name, map = new HashMap<Integer, List<ServiceMethod>>());
		}

		int parameterTypeLength = serviceMethod.getParameterTypeLength();
		List<ServiceMethod> list = map.get(parameterTypeLength);
		if (list == null) {
			map.put(parameterTypeLength, list = new ArrayList<ServiceMethod>());
		}
		list.add(serviceMethod);
		methodSize++;
	}

	public ServiceMethod getMethod(String methodName, ServiceParam paramNames) throws InvocationFailureException {
		if (methodSize == 1) {
			return currentMethod;
		}
		ServiceMethod method = getBestMatchMethodForCache(methodName, paramNames);
		if (method == null) {
			synchronized (this) {
				method = getBestMatchMethodForCache(methodName, paramNames);
				if (method == null) {
					method = getBestMatchMethod(methodName, paramNames);
					this.bestMacthMethod.get(methodName).put(paramNames, method);
				}
			}
		}
		return method;
	}

	private ServiceMethod getBestMatchMethodForCache(String methodName, ServiceParam paramNames) {
		Map<ServiceParam, ServiceMethod> map = bestMacthMethod.get(methodName);
		if (map == null) {
			bestMacthMethod.put(methodName, map = new HashMap<ServiceParam, ServiceMethod>());
		}
		return map.get(paramNames);
	}

	private ServiceMethod getBestMatchMethod(String methodName, ServiceParam paramNames) throws InvocationFailureException {
		Map<Integer, List<ServiceMethod>> methodMap = this.methods.get(methodName);
		if (methodMap == null) {
			throw new InvocationFailureException("the service " + this.service + " is not matched with method:" + methodName);
		}

		List<ServiceMethod> methodList = methodMap.get(paramNames.getLength());
		if (methodList == null || methodList.size() == 0) {
			throw new InvocationFailureException("the service " + this.service + " is not matched with method:" + methodName + " for " + paramNames.getLength()
					+ " parameters");
		}

		if (paramNames.getLength() == 0) {
			return methodList.get(0);
		}

		int matchingValue = -1;
		ServiceMethod bestMethod = null;
		for (ServiceMethod serviceMethod : methodList) {
			int matching = matching(serviceMethod, paramNames.getParamNames(), false);
			if(matching > matchingValue) {
				matchingValue = matching;
				bestMethod = serviceMethod;
			}
		}
		if(matchingValue < 0){
			for (ServiceMethod serviceMethod : methodList) {
				int matching = matching(serviceMethod, paramNames.getParamNames(), true);
				if(matching > matchingValue) {
					matchingValue = matching;
					bestMethod = serviceMethod;
				}
			}
			if (matchingValue >= 0) {
				bestMethod.setNeedCastParameterClasses(true);
			}
		}
		if (matchingValue < 0) {
			throw new InvocationFailureException("the service " + this.service + " is not matched with method:"
					+ methodName + " for parameter class types");
		}
		return bestMethod;
	}

	/**
	 * 返回匹配度 如果返回值等于参数个数，表示完全匹配 如果返回值为0---参数个数，表示部分匹配 如果返回-1，表示有不匹配项
	 * 
	 * @param method
	 * @param paramClassNames
	 * @param cast
	 * @return
	 * @throws InvocationFailureException
	 */
	private int matching(ServiceMethod method, String[] paramClassNames, boolean cast) throws InvocationFailureException {
		int k = 0;
		Class<?>[] methodParameterTypes = method.getOriginParameterTypes();
		for (int i = 0; i < paramClassNames.length; i++) {
			if (paramClassNames[i].equals(Constants.TRANSFER_NULL)) {
				continue;
			}
			Class<?> paramClass = null;
			try {
				paramClass = ClassUtils.loadClass(paramClassNames[i]);
			} catch (ClassNotFoundException e) {
				throw new InvocationFailureException("no class found for parameter:" + paramClassNames[i]);
			}
			if (paramClass == methodParameterTypes[i]) {
				k++;
			} else if (cast) {
				if (paramClassNames[i].equals(Double.class.getName())) {
					paramClass = Float.class;
				} else if (paramClassNames[i].equals(Integer.class.getName())) {
					paramClass = Short.class;
				}
				if (paramClass == methodParameterTypes[i]) {
					k++;
				}
			}
			if (!methodParameterTypes[i].isAssignableFrom(paramClass)) {
				return -1;
			}
		}
		return k;
	}

}
